﻿using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace WebsocketApp
{
    public class WebsocketNode
    {
        [Description("服务器地址")]
        public string ServerAddress { get; private set; }

        [Description("是否已经连接")]
        public bool Online { get; private set; }
        [Description("接收任务运行中")]
        public bool Receiving { get; private set; }

        private static WebsocketNode _node = null;
        private ClientWebSocket _ws;
        private ArraySegment<byte> ReadSegment;
        private Task ReadTask;
        public CancellationTokenSource ReadCancelToken = new CancellationTokenSource();

        static WebsocketNode()
        {
            if (_node is null)
                _node = new WebsocketNode();
        }

        private WebsocketNode() 
        {
            ServerAddress = Properties.Settings.Default.ServerAddress;
            //Console.WriteLine(ServerAddress);
            ReadCancelToken.Token.Register(
                () => { 
                    Console.WriteLine("Cancel Read Task."); 
                });
        }

        public static WebsocketNode CreateInstance()
        {
            return _node;
        }

        public void StartReceive(CancellationToken cancellation)
        {
            ReadTask = Task.Factory.StartNew(async _ =>
            {
                Console.WriteLine("Start Read Task.");
                Receiving = true;
                while (true)
                {
                    if (cancellation.IsCancellationRequested)
                        break;
                    if (!Online)
                        continue;
                    byte[] buf = new byte[1024];
                    ReadSegment = new ArraySegment<byte>(buf);
                    var res = await _ws.ReceiveAsync(ReadSegment, ReadCancelToken.Token);
                    string msg = Encoding.UTF8.GetString(buf).Replace("\0", "");
                    Console.WriteLine($"{msg}");
                    if (res.MessageType == WebSocketMessageType.Close)
                    {
                        Console.WriteLine($"Connection Closed, {res.CloseStatusDescription}");
                        Online = false;
                        break;
                    }
                    ParseAsJson(msg);
                    await Task.Delay(10);
                }
                Receiving = false;
                Console.WriteLine("Read Task Finish.");
            }, ReadCancelToken.Token);
        }

        public async Task SendWingbotMessage(int msg_id, List<NavigationTaskItem> points)
        {
            var msg = new GoJson()
            {
                clientId = "DotNet",
                msgId = msg_id,
                navigationTask = points
            };
            await SendJsonMessageAsync(msg);
            //Console.WriteLine("Send Finish");
        }

        public async Task SendJsonMessageAsync<T>(T json)
        {
            try
            {
                string msg = JsonConvert.SerializeObject(json);
                await Send(msg);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                return;
            }
        }

        private bool ParseAsJson(string msg)
        {
            BaseJson baseJson;
            try
            {
                baseJson = JsonConvert.DeserializeObject<BaseJson>(msg);
                switch(baseJson.msgId)
                {
                    case 203:
                        Console.WriteLine("=== Get Robot State ===");
                        Console.WriteLine(msg);
                        break;
                    default:
                        Console.WriteLine($"UnHandled Message Id: {baseJson.msgId}");
                        break;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                return false;
            }

            return true;
        }

        public async Task Send(string msg)
        {
            if (!Online)
                return;
            if (_ws.State != WebSocketState.Open && _ws.State != WebSocketState.CloseReceived)
            {
                await SetOffline();
                return;
            }
            var cts = new CancellationTokenSource();
            cts.CancelAfter(2000);
            try
            {
                await _ws.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(msg)), WebSocketMessageType.Text, true, cts.Token);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                await SetOffline();
            }
        }

        private void InitSocket()
        {
            _ws = new ClientWebSocket();
            if (ReadCancelToken.IsCancellationRequested)
            {
                ReadCancelToken.Dispose();
                ReadCancelToken = new CancellationTokenSource();
            }
            StartReceive(ReadCancelToken.Token);
        }

        private async Task SetOffline()
        {
            Online = false;
            // _ws.Dispose();
            ReadCancelToken.Cancel();
            while(true)
            {
                if(ReadTask.Status == TaskStatus.RanToCompletion)
                {
                    Console.WriteLine("Task Ran To Completion.");
                    ReadTask.Dispose();
                    break;
                }
                await Task.Delay(10);
            }
            //_ws = null;
        }

        public async Task Close()
        {
            if(_ws.State == WebSocketState.Closed)
            {
                Console.WriteLine("Already Closed.");
                await SetOffline();
                return;
            }
            try
            {
                await _ws.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "closed by client", new CancellationToken());
                await SetOffline();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }

        public async Task<bool> Connect(string addr = null)
        {
            bool ret = false;
            Uri _url;
            if (addr is null)
            {
                addr = ServerAddress;
            }
            ServerAddress = addr;

            if (_ws is null)
            {
                InitSocket();
            }
            else
            {
                if (_ws.State == WebSocketState.Open)
                {
                    Console.WriteLine("Already Connected.");
                    return true;
                }

                if (_ws.State != WebSocketState.Open)
                {
                    InitSocket();
                }
            }

            try
            {
                _url = new Uri(addr);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                await SetOffline();
                return false;
            }
            var cts = new CancellationTokenSource();
            cts.CancelAfter(2000);

            try
            {
                await _ws.ConnectAsync(_url, cts.Token);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }


            if (_ws.State == WebSocketState.Open)
            {
                ret = true;
                Online = true;
                Console.WriteLine($"Websocket Connected to Server: {addr}");
                Properties.Settings.Default.ServerAddress = ServerAddress;
                Properties.Settings.Default.Save();
            }
            else
            {
                Console.WriteLine("Connect Failed.");
                await SetOffline();
            }
            
            return ret;
        }
    }
}
